#ifndef __CXXCALL_HPP__
#define __CXXCALL_HPP__   1

#include "typedesc.hpp"
#include <cstdint>
#include <cstddef>

namespace cxxcall
{

static const size_t WSIZE = sizeof (uintptr_t);
static const size_t WBITS = WSIZE * 8;

struct av_base
{
  union fltarg
    {
      float f;
      double d;
      long double ld;
    };

  union twoint
    {
      long long ll;
      struct
        {
          int u[2];
        } i;
    };

  enum
    {
      small_struct_ret = 0x1,
      gcc_struct_ret = 0x2,
      msvc_struct_ret = 0x4,
      split_struct_ret = 0x8,
      dfl_struct_ret =
#if (defined (CXXCALL_I386) &&   \
      (defined (_WIN32) || defined (__CYGWIN__) || defined (__MACH__) ||   \
       defined (__FreeBSD__)) || defined (__OpenBSD__)) ||   \
    defined (__mipsn32__) || defined (__mips64__) ||   \
    defined (CXXCALL_ARM32) || defined (CXXCALL_ARM64) ||   \
    defined (CXXCALL_AMD64) || (defined (CXXCALL_PPC64) &&   \
                                defined (_CALL_ELF) && _CALL_ELF == 2)
        small_struct_ret |
#endif

#if defined (__GNUC__) && !(defined (__mipsn32__) || defined (__mips64__))
        gcc_struct_ret |
#endif

#if defined (CXXCALL_I386) && defined (_WIN32) && !defined (__CYGWIN__)
        msvc_struct_ret |
#endif
        0,

      sgi_struct_args = 0x100,
      aix_struct_args = 0x200,
      dfl_struct_args =
#if defined (__mips__) && !(defined (__mipsn32__) || defined (__mips64__) ||   \
                            defined (__GNUC__))
        sgi_struct_args |
#endif

#if defined (CXXCALL_PPC32) && defined (_AIX) && !defined (__GNUC__)
        aix_struct_args |
#endif
        0,

      aix_flt_args = 0x10000,
#if defined (CXXCALL_PPC64) && defined (_AIX) && !defined (__GNUC__)
      dfl_flt_args = aix_flt_args,
#else
      dfl_flt_args = 0,
#endif

      dfl_flags = dfl_struct_ret | dfl_struct_args | dfl_flt_args,

      variadic_fn = 1 << 23,
      stdcall_fn = 1 << 24,
      reg_struct_ret = 1 << 25,
      arg_overflow = 1 << 26
    };

  unsigned int flags;
  void *func;
  void *raddr;
  int rtype;
  size_t rsize;

#if defined (CXXCALL_AMD64) && !defined (_WIN32)
  static const int IARG_NUM = 6;
  static const int FARG_NUM = 8;

  unsigned int ianum;
  uintptr_t iargs[IARG_NUM];
  double fargs[FARG_NUM];
  double *fptr;

#  define CXXCALL_XINIT_DEFINED

  void xinit ()
    {
      this->ianum = 0;
      this->fptr = this->fargs;
    }

#endif

#if defined (CXXCALL_MIPS32) && !defined (__mipsn32__) && !defined (__mips64__)
  static const int FARG_NUM = 2;

  unsigned int anum;
  unsigned int fnum;
  unsigned int farg_mask;
  unsigned int darg_mask;
  float fargs[FARG_NUM];
  double dargs[FARG_NUM];

#  define CXXCALL_XINIT_DEFINED

  void xinit ()
    {
      this->anum = this->fnum = 0;
      this->farg_mask = this->darg_mask = 0;
    }

#endif

#if defined (__mipsn32__) || defined (__mips64__)
  unsigned int anum;
  unsigned int farg_mask;
  unsigned int darg_mask;
  float fargs[8];

#  define CXXCALL_XINIT_DEFINED

  void xinit ()
    {
      this->anum = this->farg_mask = this->darg_mask = 0;
    }

#endif

#if defined (__armhf__)

  static const int IARG_NUM = 4;

  uintptr_t *iarg;
  unsigned int ianum;
  unsigned int fanum;
  unsigned int farg_mask;
  unsigned int darg_mask;
  float fargs[16];
  double dargs[8];

#  define CXXCALL_XINIT_DEFINED

  void xinit ()
    {
      this->ianum = this->fanum = 0;
      this->farg_mask = this->darg_mask = 0;
    }

#endif

#if defined (CXXCALL_ARM64)
  static const int IARG_NUM = 8;
  static const int FARG_NUM = 8;

  unsigned int ianum;
  uintptr_t iargs[IARG_NUM];
  unsigned int fanum;
  unsigned char farg_types[FARG_NUM];
  fltarg fargs[FARG_NUM];

#  define CXXCALL_XINIT_DEFINED

  void xinit ()
    {
      this->ianum = this->fanum = 0;
    }

#endif

#if defined (CXXCALL_PPC32) || defined (CXXCALL_PPC64)
#  if defined (_AIX) || (defined (__MACH__) && defined (__APPLE__))
  static const int FARG_NUM = 13;
#  else
  static const int FARG_NUM = 8;
  static const int IARG_NUM = 8;

  unsigned int ianum;
  uintptr_t iargs[IARG_NUM];
#  endif

  double fargs[FARG_NUM];
  double *fptr;

#  define CXXCALL_XINIT_DEFINED

  void xinit ()
    {
#if !(defined (_AIX) || (defined (__MACH__) && defined (__APPLE__)))
      this->ianum = 0;
#endif
      this->fptr = this->fargs;
    }

#endif

#if defined (CXXCALL_AMD64) && defined (_WIN32)
  static const int FARG_NUM = 4;

  unsigned int anum;
  unsigned int fnum;
  double fargs[FARG_NUM];

#  define CXXCALL_XINIT_DEFINED

  void xinit ()
    {
      this->anum = this->fanum = 0;
    }

#endif

#ifndef CXXCALL_XINIT_DEFINED
  void xinit () {}
#endif

#undef CXXCALL_XINIT_DEFINED
};

struct alignas (std::max_align_t) av_data : public av_base
{
  uintptr_t *args;
  uintptr_t *curp;
  uintptr_t *endp;

  void _Reg_struct_ret ();

  void init (uintptr_t *argp, size_t nargs, void *fct,
      void *retaddr, int rettype, size_t retsize, unsigned int flg)
    {
      this->xinit ();
      this->args = this->curp = argp;
      this->endp = this->args + nargs;
      this->func = (void *)fct;
      this->raddr = retaddr;
      this->rtype = rettype;
      this->rsize = retsize;
      this->flags = flg;

      if (rettype == type::struct_t &&
          (this->flags & small_struct_ret))
        this->_Reg_struct_ret ();

#ifdef __armhf__
      this->curp += IARG_NUM;
#endif
    }

  bool set_overflow ()
    {
      this->flags |= arg_overflow;
      return (false);
    }

  bool arg_t (intptr_t val);
  bool arg_ut (uintptr_t val)
    {
      return (this->arg_t ((intptr_t)val));
    }

  bool arg (float val);
  bool arg (double val);


  bool arg (long double val)
#ifdef CXXCALL_LONG_DOUBLE_ALIASED
    {
      return (this->arg ((double)val));
    }
#else
  ;
#endif

#define ARG_(type, suffix)   \
  bool arg (type val)   \
    {   \
      return (this->arg_##suffix (val));   \
    }

  ARG_(signed char, t)
  ARG_(unsigned char, ut)
  ARG_(short, t)
  ARG_(unsigned short, ut)
  ARG_(int, t)
  ARG_(unsigned int, ut)
  ARG_(long, t)
  ARG_(unsigned long, ut)

#ifdef CXXCALL_WIDE
  ARG_(long long, t)
  ARG_(unsigned long long, ut)
#else
  bool arg (long long val);
  bool arg (unsigned long long val)
    {
      return (this->arg ((long long)val));
    }
#endif

#undef ARG_

  bool arg (const void *ptr)
    {
      return (this->arg ((uintptr_t)ptr));
    }

  bool arg (const void *data, size_t size, size_t align);

  bool call ();

  bool error () const
    {
      return ((this->flags & arg_overflow) != 0);
    }
};

struct cb_data : public av_base
{
  union
    {
      signed char sch;
      unsigned char uch;
      short shrt;
      unsigned short ushrt;
      int ival;
      unsigned int uival;
      long lval;
      unsigned long ulval;
      long long llval;
      unsigned long long ullval;
      twoint parts;
      double dbl;
      float flt;
      long double ldbl;
    } tmp;

  uintptr_t curr;

#if defined (CXXCALL_I386) || defined (CXXCALL_ARM64)
  void *structaddr;
#endif

#if defined (CXXCALL_MIPS) && !defined (__mipsn32__) && !defined (__mips64__)
  uintptr_t memarg;
#endif

  bool _Reg_struct_ret ();

  void init (unsigned int flg, int rettype)
    {
      this->xinit ();
      this->flags = flg;

      if ((this->rtype = rettype) == type::struct_t &&
          (this->flags & small_struct_ret) &&
          this->_Reg_struct_ret ())
        {
          this->raddr = &this->tmp;
          this->flags |= reg_struct_ret;
        }
    }

  template <class T>
  void start (unsigned int flg = dfl_flags)
    {
      this->init (flg, typedesc<T>::code);
    }

  static size_t arg_size (size_t size)
    {
      return ((size + WSIZE - 1) & ~(WSIZE - 1));
    }

  static size_t struct_alignment (size_t align)
    {
#if defined (_WIN32) && defined (CXXCALL_I386)
      return (align <= 4 ? align : 4);
#else
      return (align);
#endif
    }

  void* xarg (size_t size, size_t align, bool integral, bool on_stack);

  void* arg (size_t size, size_t align, bool on_stack)
    {
      return (this->xarg (size, align, false, on_stack));
    }

  template <class T>
  T arg ()
    {
      return (*(T *)this->xarg (sizeof (T), alignof (T), true, false));
    }

#ifndef CXXCALL_WIDE
  long long arg_ll ();
#endif

  float arg_f ();
  double arg_d ();

#ifdef CXXCALL_LONG_DOUBLE_ALIASED
  long double arg_ld ()
    {
      return (this->arg_d ());
    }
#else
  long double arg_ld ();
#endif

  void _Assert_rtype (int);

  void ret ()
    {
      this->_Assert_rtype (type::void_t);
    }

  template <class T>
  void ret (const T& val)
    {
      int type = typedesc<T>::code;
      this->_Assert_rtype (type);

      void *ptr = type == type::struct_t ? this->raddr : &this->tmp;
      *(T *)ptr = val;
    }

  static void* alloc (void (*fct) (cb_data *, void *), void *arg);
  static void dealloc (void *mem);
  static void fill (void *outp, void (*fct) (cb_data *, void *), void *arg);
};

#ifndef CXXCALL_WIDE

template <>
inline long long cb_data::arg<long long> ()
{
  return (this->arg_ll ());
}

template <>
inline unsigned long long cb_data::arg<unsigned long long> ()
{
  return ((unsigned long long)this->arg_ll ());
}

#endif

template <>
inline void* cb_data::arg<void *> ()
{
  return ((void *)this->arg<uintptr_t> ());
}

template <>
inline float cb_data::arg<float> ()
{
  return (this->arg_f ());
}

template <>
inline double cb_data::arg<double> ()
{
  return (this->arg_d ());
}

template <>
inline long double cb_data::arg<long double> ()
{
  return (this->arg_ld ());
}

/* Determine if a structure is 'splittable'; i.e: if its contents may
 * be returned in more than 1 register. */

template <class T, class M1>
bool splittable_p (M1 T::*)
{
  return ((sizeof (M1) - 1) / WSIZE == 0);
}

template <class T1, class T2>
constexpr size_t _Offsetof2 ()
{
  return ((sizeof (T1) + alignof (T2) - 1) & -(long)alignof (T2));
}

template <class T, class M1, class M2>
bool splittable_p (M1 T::*m1, M2 T::*)
{
  return (splittable_p (m1) &&
    (_Offsetof2<M1, M2> / WSIZE ==
      (_Offsetof2<M1, M2> + sizeof (M2) - 1) / WSIZE));
}

template <class T1, class T2, class T3>
constexpr size_t _Offsetof3 ()
{
  return ((_Offsetof2<T1, T2> + alignof (T3)) & -(long)alignof (T3));
}

template <class T, class M1, class M2, class M3>
bool splittable_p (M1 T::*m1, M2 T::*m2, M3 T::*)
{
  return (splittable_p (m1, m2) &&
    (_Offsetof3<M1, M2, M3> / WSIZE ==
      (_Offsetof3<M1, M2, M3> + sizeof (M3) - 1) / WSIZE));
}

template <class T1, class T2, class T3, class T4>
constexpr size_t _Offsetof4 ()
{
  return ((_Offsetof2<T1, T2, T3> + alignof (T4)) & -(long)alignof (T4));
}

template <class T, class M1, class M2, class M3, class M4>
bool splittable_p (M1 T::*m1, M2 T::*m2, M3 T::*m3, M4 T::*)
{
  return (splittable_p (m1, m2, m3) &&
    (_Offsetof4<M1, M2, M3, M4> / WSIZE ==
      (_Offsetof4<M1, M2, M3, M4> + sizeof (M4) - 1) / WSIZE));
}

// Like 'enable_if', but don't pull <type_traits> for it.
template <bool B>
struct enable
{
};

template <>
struct enable<true>
{
  typedef int type;
};

template <class T, size_t N>
struct av_static
{
  typedef typename typedesc<T>::rtype rtype;
  rtype retval;
  uintptr_t st_args[N];
  av_data av;

  av_static (void *fn, unsigned int flg)
    {
      this->av.init (this->st_args, N, fn, &this->retval,
        typedesc<T>::code, sizeof (this->retval), flg);
    }

  template <class T2>
  bool arg (T2 value)
    {
      return (this->av.arg (value));
    }

  template <class T2>
  bool arg (const T2& val,
      typename enable<typedesc<T2>::code == type::struct_t>::type = 0)
    {
      return (this->av.arg (&val, sizeof (val), alignof (T2)));
    }

  bool error () const
    {
      return (this->av.error ());
    }

  rtype call ()
    {
      this->av.call ();
      return (this->retval);
    }
};

template <size_t S1, size_t ...SN>
struct slot_sum
{
  static const size_t nslots = S1 + slot_sum<SN...>::nslots;
};

template <size_t S1>
struct slot_sum<S1>
{
  static const size_t nslots = S1;
};

#ifdef __armhf__
#  define CXXCALL_ESLOTS   4
#else
#  define CXXCALL_ESLOTS   0
#endif

#define CXXCALL_SLOTS1(T)   typedesc<T>::nslots + CXXCALL_ESLOTS
#define CXXCALL_VSLOTS(T)   \
  slot_sum<typedesc<T>::nslots...>::nslots + CXXCALL_ESLOTS

template <class T>
auto make_av_static (T (*fn) (), unsigned int flg = av_data::dfl_flags) ->
  decltype (av_static<T, CXXCALL_SLOTS1 (T)> (0, 0))
{
  av_static<T, CXXCALL_SLOTS1 (T)> ret ((void *)fn, flg);
  return (ret);
}

template <class T, class ...Args>
auto make_av_static (T (*fn) (Args...),
                     unsigned int flg = av_data::dfl_flags) ->
  decltype (av_static<T, CXXCALL_VSLOTS (Args)> (0, 0))
{
  av_static<T, CXXCALL_VSLOTS (Args)> ret ((void *)fn, flg);
  return (ret);
}

template <class T, class ...Args>
auto make_av_static (T (*fn) (Args..., ...),
                     unsigned int flg = av_data::dfl_flags) ->
  decltype (av_static<T, CXXCALL_VSLOTS (Args)> (0, 0))
{
  av_static<T, CXXCALL_VSLOTS (Args)>
    ret ((void *)fn, flg | av_data::variadic_fn);
  return (ret);
}

#undef CXXCALL_ESLOTS
#undef CXXCALL_SLOTS1
#undef CXXCALL_VSLOTS

} // namespace cxxcall

#endif
